<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<input type="text" v-model="text" />
			{{ text }}
		</div>

		<script>
			function observe(obj, vm) {
				Object.keys(obj).forEach((key) => {
					defineReactive(vm, key, obj[key])
				})
			}

			function defineReactive(obj, key, val) {
				var dep = new Dep()
				Object.defineProperty(obj, key, {
					get() {
						// 添加订阅者 Watcher 到主题对象 Dep
						if (Dep.target) dep.addSub(Dep.target)
						return val
					},
					set(newVal) {
						if (newVal === val) return
						val = newVal
						// 作为发布者发出通知
						dep.notify()
					},
				})
			}

			// 文档碎片函数
			function nodeToFragment(node, vm) {
				var flag = document.createDocumentFragment()
				var child
				while ((child = node.firstChild)) {
					compile(child, vm)
					flag.appendChild(child) // 劫持node 所有子节点
				}
				return flag
			}

			// 编译模板函数,每个 data 中的节点实例化一个 watcher
			function compile(node, vm) {
				var reg = /\{\{(.*)\}\}/
				if (node.nodeType === 1) {
					var attr = node.attributes
					// 解析属性
					for (let i = 0; i < attr.length; i++) {
						if (attr[i].nodeName === 'v-model') {
							var name = attr[i].nodeValue // 获取 v-model 绑定的属性名
							node.addEventListener('input', (e) => {
								// 给相应的 data 属性赋值，进而触发该属性的 set 方法
								vm[name] = e.target.value
							})
							node.value = vm[name] // 将 data 的值赋给该 node
							node.removeAttribute('v-model')
						}
					}

					new Watcher(vm, node, name, 'input')
				}
				// 节点类型为 text
				if (node.nodeType === 3) {
					if (reg.test(node.nodeValue)) {
						var name = RegExp.$1 // RegExp.$1: 获取匹配到的第一个()字符串
						name = name.trim()
						new Watcher(vm, node, name, 'text')
					}
				}
			}

			// 编译 html 过程，为每个与 data 关联的节点生成一个 Watcher
			function Watcher(vm, node, name, nodeType) {
				Dep.target = this
				this.name = name
				this.node = node
				this.vm = vm
				this.nodeType = nodeType
				this.update()
				Dep.target = null
			}

			Watcher.prototype = {
				update() {
					this.get()
					if (this.nodeType == 'text') {
						this.node.nodeValue = this.value
					}
					if (this.nodeType == 'input') {
						this.node.value = this.value
					}
				},
				// 获取 data 中的属性值
				get() {
					this.value = this.vm[this.name] // 触发相应属性的 get
				},
			}

			function Dep() {
				this.subs = []
			}

			Dep.prototype = {
				addSub(sub) {
					this.subs.push(sub)
				},

				notify() {
					this.subs.forEach((sub) => {
						sub.update()
					})
				},
			}

			function Vue(options) {
				this.data = options.data
				var data = this.data
				observe(data, this)

				var id = options.el
				var dom = nodeToFragment(document.getElementById(id), this)
				// 编译完成后，将 dom 返回到 app 中
				document.getElementById(id).appendChild(dom)
			}

			var vm = new Vue({
				el: 'app',
				data: {
					text: 'hellow world',
				},
			})
		</script>
	</body>
</html>
